查看原文
其他

用RecyclerView打造一个轮播图

大头呆 鸿洋 2019-04-05

每日推荐


推荐一篇文章,《"放弃Jni"愉快的奔向JNA》,从某种程度上看JNA要比JNI方便很多,当然也会有一些性能上的损失,如果有兴趣,不妨扩充下知识面。


http://www.jianshu.com/p/fdd7777ca522


本文作者


作者:大头呆

链接:

http://www.jianshu.com/p/1e2baec44e0d

本文由作者授权发布。


通常Android的轮播图(俗名:Banner)都是用ViewPager实现的,但是我在实际项目运用中碰到了一些小问题,于是决定另寻思路,采用RecyclerView这个更优雅更强大的空间来实现轮播的功能,顺便复习下RecyclerView的相关知识。


1实现


一般轮播图就两个重要的部分:


可以无限左右滑动的图片流和图片位置的标示点,可能更简单的连指示点都省略了。


主要的难点还是在前者,因为一个轮播图要播放的图片一般也就十来张,不做任何处理直接塞到RecyclerView里面,不仅稍微滑一下就没了而且开始还不能先往左边滑,所以我们需要在设置Adapter的总数时设置成一个比较大的数(可以是Integer.MAX_VALUE),然后在设置完图片数据后把RecyclerView的当前位置转到中间的一个数(为了保证从第一张开始播放,必须是图片总数的倍数,比如10000*size),这样item回收复用的时候,我们只要取当前位置和图片数量的余数,得到真正的图片位置。


听起来有点复杂,还是直接看下代码吧:


private class RecyclerAdapter extends RecyclerView.Adapter {
   List<String> urlList;
   public void setData(List<String> urlList) {
       this.urlList = urlList;
   }
   @Override
   public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       return new RecyclerView.ViewHolder(new ImageView(getContext())) { };
   }
   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       if (urlList == null || urlList.isEmpty())
           return;
       String url = urlList.get(position % bannerSize);
       Glide.with(getContext()).load(url).into((ImageView) holder.itemView);
   }
   @Override
   public int getItemCount() {
     //如果只有一张图片就不滑动了
       return bannerSize < 2 ? 1 : Integer.MAX_VALUE;
   }
}


至于自动滑动图片,就用Handler不断延迟发送消息就好了:


private Handler mHandler = new Handler(new Handler.Callback() {
   @Override
   public boolean handleMessage(Message msg) {
       if (msg.what == WHAT_AUTO_PLAY) {
               mRecyclerView.smoothScrollToPosition(++currentIndex);
            refreshIndicator();
               mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
       }
       return false;
   }
});


好了,无限轮播解决了,接下来就是标示点了,既然无限轮播图都用RecyclerView解决了,那么标示点也用它来解决吧:


private class IndicatorAdapter extends RecyclerView.Adapter {
   int currentPosition = 0;
   public void setPosition(int currentPosition) {
       this.currentPosition = currentPosition;
   }
   @Override
   public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       return new RecyclerView.ViewHolder(new ImageView(getContext())) {
       };
   }
   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       ImageView bannerPoint = (ImageView) holder.itemView;
       bannerPoint.setImageDrawable(currentPosition == position ? mSelectedDrawable : mUnselectedDrawable);
   }
   @Override
   public int getItemCount() {
       return bannerSize;
   }
}


其实Adapter也很简单,设置一个当前位置的标识点,然后在图片改变的时候notifyDataSetChanged()就行了。


好了最后就剩下怎么监听RecyclerView的位置改变了(可没有像Viewpager的addOnPageChangeListener那么直接的方法),没办法直接分析RecyclerView.OnScrollListener中的回调方法吧:


mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
   @Override
   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
       //解决连续滑动时指示器不更新的问题
       if (bannerSize < 2) return;
       int firstReal = mLinearLayoutManager.findFirstVisibleItemPosition();
       View viewFirst = mLinearLayoutManager.findViewByPosition(firstReal);
       float width = getWidth();
       if (width != 0 && viewFirst != null) {
           float right = viewFirst.getRight();
           float ratio = right / width;
           if (ratio > 0.8) {
               if (currentIndex != firstReal) {
                   currentIndex = firstReal;
                   refreshIndicator();
               }
           } else if (ratio < 0.2) {
               if (currentIndex != firstReal + 1) {
                   currentIndex = firstReal + 1;
                   refreshIndicator();
               }
           }
       }
   }
   @Override
   public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   //连续滑动时可能不会回调
       int first = mLinearLayoutManager.findFirstVisibleItemPosition();
       int last = mLinearLayoutManager.findLastVisibleItemPosition();
       if (currentIndex != first && first == last) {
           currentIndex = first;
           refreshIndicator();
       }
   }
});


看看成果


好了解决了这些东西,再加些自定义View常用的属性,回调方法,设置的接口,轮播图就做好了,我们来看看效果:



嗯,看着还不错,可是怎么有点怪?


唉,这图片怎么滑动的这么快,而且还能停在中间,这个不是我们想要的`标准`轮播图。要解决这个问题就要用到RecyclerView的另一个功能:SnapHelper


SnapHelper旨在支持RecyclerView的对齐方式,也就是通过计算对齐RecyclerView中TargetView 的指定点或者容器中的任何像素点。


自定义一个SnapHelper挺麻烦的,还好android已经为我们内置好了两个实现: LinearSnapHelper & PagerSnapHelper。其中PagerSnapHelper真是我们需要的可以把RecyclerView改的像Viewpager的工具。


new PagerSnapHelper().attachToRecyclerView(mRecyclerView);




现在看着顺畅多了:一次只能滑动一张图片,停止的时候图片的位置也对了。这样一个基础版的轮播图就做成了。


因为本质是一个RecyclerView,我们可以RecyclerView.Itemanimator,来做出更多的动画效果(这个我目前就不太会了(T_T))。最后奉上github地址,里面有更完整代码,封装了很多自定义属性,欢迎star!


https://github.com/renjianan/RecyclerBanner


看到这里,希望大家可以用RecyclerView尝试下,当然可能也有人觉得这和ViewPager比起来没有优势哇,那只能贴个进阶版本的图了:



会在后续推送给大家,迫不及待的也可以直接去作者博客列表学习。


推荐阅读

上一篇:Android APP 性能优化的一些思考

封装RecyclerView自动加载更多 & 下拉刷新



如果你想要跟大家分享你的文章,欢迎投稿~


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存